package org.example;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ApplicationContext {

    private final Map<String, BeanDefination> beanDefinationMap = new HashMap<>();
    private final List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
    private final Map<String, Object> ioc = new HashMap<>();
    private final Map<String, Object> loadingIoc = new HashMap<>();
    public ApplicationContext(String packagename) throws URISyntaxException, IOException {
        initContext(packagename);
    }

    public void initContext(String packageName) throws URISyntaxException, IOException {
        //扫描class资源并将@Conpanent注解修饰的类包装成BeanDifination
        scanPackage(packageName).stream().filter(this::canCreate).forEach(this::wrap);
        //将BeanPostProcessor的实现类的bean创建出来，为普通bean的创建做准备
        initBeanPostProcessor();
        //创建普通bean
        beanDefinationMap.values().stream().forEach(this::createBean);
    }

    private void initBeanPostProcessor(){
        //对BeanPostProcessor类的实现类进行创建
        beanDefinationMap.values().stream().filter(bd->BeanPostProcessor.class.isAssignableFrom(bd.getClass()))
                .map(this::createBean)
                .map(BeanPostProcessor.class::cast)
                .forEach(beanPostProcessorList::add);
    }

    private Object createBean(BeanDefination beanDefination){
        String name = beanDefination.getName();
        //首先从ioc容器(第一级缓存)拿取bean
        if(ioc.containsKey(name)){
            return ioc.get(name);
        }
        //一级缓存没有，从第二级缓存拿取bean
        if(loadingIoc.containsKey(name)){
            return loadingIoc.get(name);
        }
        //都没有则创建
        return doCreate(beanDefination);
    }

    private Object doCreate(BeanDefination beanDefination){
        Object bean = null;
        String name = beanDefination.getName();
        Constructor<?> constructor = beanDefination.getConstructor();
        try {
            //实例化(分配内存)：调用构造器
            bean = constructor.newInstance();
            //将半成品bean添加到二级缓存
            loadingIoc.put(name, bean);
            //依赖注入
            autowiredBean(bean, beanDefination);
            //调用beanpostprocessor自定义bean
            bean = initBean(bean, beanDefination);
            //删除二级缓存的bean，并在一级缓存放入bean
            loadingIoc.remove(name);
            ioc.put(name, bean);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return bean;
    }

    private Object initBean(Object bean, BeanDefination beanDefination) throws InvocationTargetException, IllegalAccessException {
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            bean = beanPostProcessor.beforeInitializeBean(bean, beanDefination.getName());
        }

        Method postConstructMethod = beanDefination.getPostConstructMethod();
        System.out.println("getName " + beanDefination.getName());
        if(postConstructMethod != null){
            postConstructMethod.invoke(bean);
        }
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            bean = beanPostProcessor.afterInitializeBean(bean, beanDefination.getName());
        }

        return bean;
    }

    private void autowiredBean(Object bean, BeanDefination beanDefination) {
        for (Field field : beanDefination.getAutowiredfieldList()) {
            field.setAccessible(true);
            try {
                //注入依赖
                field.set(bean, getBean(field.getType()));
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private Object getBean(String beanName) {
        //从ioc容器中拿取bean，注意：在依赖注入过程，注入的依赖还没有创建，所以在这里还需要调用createBean函数
        if(beanName == null){
            return null;
        }
        Object bean = this.ioc.get(beanName);
        if(bean != null){
            //如果依赖已经存在
            return bean;
        }
        //依赖还没有
        if(beanDefinationMap.containsKey(beanName)){
            return createBean(beanDefinationMap.get(beanName));
        }

        return null;
    }

    private Object getBean(Class<?> type) {
        //遍历beanDefinationMap，找到对应Class类型的beanName
        String name = this.beanDefinationMap.values().stream()
                .filter(bd->type.isAssignableFrom(bd.getBeanType()))
                .map(BeanDefination::getName)
                .findFirst()
                .orElse(null);
        //最终使用beanName获取bean
        return getBean(name);
    }

    private boolean canCreate(Class<?> type){
        //帅选加了@Componet注解的类进行创建bean
        return type.isAnnotationPresent(Componet.class);
    }

    private BeanDefination wrap(Class<?> type){
        //将Class信息打包成BeanDifination
        BeanDefination beanDefination = new BeanDefination(type);
        if(beanDefinationMap.containsKey(beanDefination.getName())){
            throw new RuntimeException("bean名字重复");
        }
//        System.out.println("beanDefination.getName() : " + beanDefination.getName());
        beanDefinationMap.put(beanDefination.getName(), beanDefination);
        return beanDefination;
    }

    private List<Class<?>> scanPackage(String packageName) throws URISyntaxException, IOException {
        List<Class<?>> classList = new ArrayList<>();
        //根据packagename获取包下的资源
        URL resource = this.getClass().getClassLoader().getResource(packageName.replace(".", File.separator));
        Path path = Path.of(resource.toURI());
        //递归遍历包下的文件
        Files.walkFileTree(path, new SimpleFileVisitor<>(){
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Path absoulutePath = file.toAbsolutePath();
                //只需要以“.class”结尾的文件资源
                if(absoulutePath.toString().endsWith(".class")){
                    //获取全类名
                    String replaceStr = absoulutePath.toString().replace(File.separator, ".");
                    int packageIdx = replaceStr.indexOf(packageName);
                    String className = replaceStr.substring(packageIdx, replaceStr.length() - ".class".length());
                    try {
                        //通过反射获取Class
                        classList.add(Class.forName(className));
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
                return FileVisitResult.CONTINUE;
            }
        });

        return  classList;
    }
}
